/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version: 1.3.8
 *
 */
(function($) {

    $.fn.extend({
        slimScroll: function(options) {

            var defaults = {

                // width in pixels of the visible scroll area
                width: "auto",

                // height in pixels of the visible scroll area
                height: "250px",

                // width in pixels of the scrollbar and rail
                size: "7px",

                // scrollbar color, accepts any hex/color value
                color: "#000",

                // scrollbar position - left/right
                position: "right",

                // distance in pixels between the side edge and the scrollbar
                distance: "1px",

                // default scroll position on load - top / bottom / $('selector')
                start: "top",

                // sets scrollbar opacity
                opacity: .4,

                // enables always-on mode for the scrollbar
                alwaysVisible: false,

                // check if we should hide the scrollbar when user is hovering over
                disableFadeOut: false,

                // sets visibility of the rail
                railVisible: false,

                // sets rail color
                railColor: "#333",

                // sets rail opacity
                railOpacity: .2,

                // whether  we should use jQuery UI Draggable to enable bar dragging
                railDraggable: true,

                // defautlt CSS class of the slimscroll rail
                railClass: "slimScrollRail",

                // defautlt CSS class of the slimscroll bar
                barClass: "slimScrollBar",

                // defautlt CSS class of the slimscroll wrapper
                wrapperClass: "slimScrollDiv",

                // check if mousewheel should scroll the window if we reach top/bottom
                allowPageScroll: false,

                // scroll amount applied to each mouse wheel step
                wheelStep: 20,

                // scroll amount applied when user is using gestures
                touchScrollStep: 200,

                // sets border radius
                borderRadius: "7px",

                // sets border radius of the rail
                railBorderRadius: "7px"
            };

            var o = $.extend(defaults, options);

            // do it for every element that matches selector
            this.each(function() {

                var isOverPanel,
                    isOverBar,
                    isDragg,
                    queueHide,
                    touchDif,
                    barHeight,
                    percentScroll,
                    lastScroll,
                    divS = "<div></div>",
                    minBarHeight = 30,
                    releaseScroll = false;

                // used in event handlers and for better minification
                var me = $(this);

                // ensure we are not binding it again
                if (me.parent().hasClass(o.wrapperClass)) {
                    // start from last bar position
                    var offset = me.scrollTop();

                    // find bar and rail
                    bar = me.siblings("." + o.barClass);
                    rail = me.siblings("." + o.railClass);

                    getBarHeight();

                    // check if we should scroll existing instance
                    if ($.isPlainObject(options)) {
                        // Pass height: auto to an existing slimscroll object to force a resize after contents have changed
                        if ("height" in options && options.height == "auto") {
                            me.parent().css("height", "auto");
                            me.css("height", "auto");
                            var height = me.parent().parent().height();
                            me.parent().css("height", height);
                            me.css("height", height);
                        } else if ("height" in options) {
                            var h = options.height;
                            me.parent().css("height", h);
                            me.css("height", h);
                        }

                        if ("scrollTo" in options) {
                            // jump to a static point
                            offset = parseInt(o.scrollTo);
                        } else if ("scrollBy" in options) {
                            // jump by value pixels
                            offset += parseInt(o.scrollBy);
                        } else if ("destroy" in options) {
                            // remove slimscroll elements
                            bar.remove();
                            rail.remove();
                            me.unwrap();
                            return;
                        }

                        // scroll content by the given offset
                        scrollContent(offset, false, true);
                    }

                    return;
                } else if ($.isPlainObject(options)) {
                    if ("destroy" in options) {
                        return;
                    }
                }

                // optionally set height to the parent's height
                o.height = (o.height == "auto") ? me.parent().height() : o.height;

                // wrap content
                var wrapper = $(divS)
                    .addClass(o.wrapperClass)
                    .css({
                        position: "relative",
                        overflow: "hidden",
                        width: o.width,
                        height: o.height
                    });

                // update style for the div
                me.css({
                    overflow: "hidden",
                    width: o.width,
                    height: o.height
                });

                // create scrollbar rail
                var rail = $(divS)
                    .addClass(o.railClass)
                    .css({
                        width: o.size,
                        height: "100%",
                        position: "absolute",
                        top: 0,
                        display: (o.alwaysVisible && o.railVisible) ? "block" : "none",
                        'border-radius': o.railBorderRadius,
                        background: o.railColor,
                        opacity: o.railOpacity,
                        zIndex: 90
                    });

                // create scrollbar
                var bar = $(divS)
                    .addClass(o.barClass)
                    .css({
                        background: o.color,
                        width: o.size,
                        position: "absolute",
                        top: 0,
                        opacity: o.opacity,
                        display: o.alwaysVisible ? "block" : "none",
                        'border-radius': o.borderRadius,
                        BorderRadius: o.borderRadius,
                        MozBorderRadius: o.borderRadius,
                        WebkitBorderRadius: o.borderRadius,
                        zIndex: 99
                    });

                // set position
                var posCss = (o.position == "right") ? { right: o.distance } : { left: o.distance };
                rail.css(posCss);
                bar.css(posCss);

                // wrap it
                me.wrap(wrapper);

                // append to parent div
                me.parent().append(bar);
                me.parent().append(rail);

                // make it draggable and no longer dependent on the jqueryUI
                if (o.railDraggable) {
                    bar.bind("mousedown",
                        function(e) {
                            var $doc = $(document);
                            isDragg = true;
                            t = parseFloat(bar.css("top"));
                            pageY = e.pageY;

                            $doc.bind("mousemove.slimscroll",
                                function(e) {
                                    currTop = t + e.pageY - pageY;
                                    bar.css("top", currTop);
                                    scrollContent(0, bar.position().top, false); // scroll content
                                });

                            $doc.bind("mouseup.slimscroll",
                                function(e) {
                                    isDragg = false;
                                    hideBar();
                                    $doc.unbind(".slimscroll");
                                });
                            return false;
                        }).bind("selectstart.slimscroll",
                        function(e) {
                            e.stopPropagation();
                            e.preventDefault();
                            return false;
                        });
                }

                // on rail over
                rail.hover(function() {
                        showBar();
                    },
                    function() {
                        hideBar();
                    });

                // on bar over
                bar.hover(function() {
                        isOverBar = true;
                    },
                    function() {
                        isOverBar = false;
                    });

                // show on parent mouseover
                me.hover(function() {
                        isOverPanel = true;
                        showBar();
                        hideBar();
                    },
                    function() {
                        isOverPanel = false;
                        hideBar();
                    });

                // support for mobile
                me.bind("touchstart",
                    function(e, b) {
                        if (e.originalEvent.touches.length) {
                            // record where touch started
                            touchDif = e.originalEvent.touches[0].pageY;
                        }
                    });

                me.bind("touchmove",
                    function(e) {
                        // prevent scrolling the page if necessary
                        if (!releaseScroll) {
                            e.originalEvent.preventDefault();
                        }
                        if (e.originalEvent.touches.length) {
                            // see how far user swiped
                            var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep;
                            // scroll content
                            scrollContent(diff, true);
                            touchDif = e.originalEvent.touches[0].pageY;
                        }
                    });

                // set up initial height
                getBarHeight();

                // check start position
                if (o.start === "bottom") {
                    // scroll content to bottom
                    bar.css({ top: me.outerHeight() - bar.outerHeight() });
                    scrollContent(0, true);
                } else if (o.start !== "top") {
                    // assume jQuery selector
                    scrollContent($(o.start).position().top, null, true);

                    // make sure bar stays hidden
                    if (!o.alwaysVisible) {
                        bar.hide();
                    }
                }

                // attach scroll events
                attachWheel(this);

                function _onWheel(e) {
                    // use mouse wheel only when mouse is over
                    if (!isOverPanel) {
                        return;
                    }

                    var e = e || window.event;

                    var delta = 0;
                    if (e.wheelDelta) {
                        delta = -e.wheelDelta / 120;
                    }
                    if (e.detail) {
                        delta = e.detail / 3;
                    }

                    var target = e.target || e.srcTarget || e.srcElement;
                    if ($(target).closest("." + o.wrapperClass).is(me.parent())) {
                        // scroll content
                        scrollContent(delta, true);
                    }

                    // stop window scroll
                    if (e.preventDefault && !releaseScroll) {
                        e.preventDefault();
                    }
                    if (!releaseScroll) {
                        e.returnValue = false;
                    }
                }

                function scrollContent(y, isWheel, isJump) {
                    releaseScroll = false;
                    var delta = y;
                    var maxTop = me.outerHeight() - bar.outerHeight();

                    if (isWheel) {
                        // move bar with mouse wheel
                        delta = parseInt(bar.css("top")) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight();

                        // move bar, make sure it doesn't go out
                        delta = Math.min(Math.max(delta, 0), maxTop);

                        // if scrolling down, make sure a fractional change to the
                        // scroll position isn't rounded away when the scrollbar's CSS is set
                        // this flooring of delta would happened automatically when
                        // bar.css is set below, but we floor here for clarity
                        delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);

                        // scroll the scrollbar
                        bar.css({ top: delta + "px" });
                    }

                    // calculate actual scroll amount
                    percentScroll = parseInt(bar.css("top")) / (me.outerHeight() - bar.outerHeight());
                    delta = percentScroll * (me[0].scrollHeight - me.outerHeight());

                    if (isJump) {
                        delta = y;
                        var offsetTop = delta / me[0].scrollHeight * me.outerHeight();
                        offsetTop = Math.min(Math.max(offsetTop, 0), maxTop);
                        bar.css({ top: offsetTop + "px" });
                    }

                    // scroll content
                    me.scrollTop(delta);

                    // fire scrolling event
                    me.trigger("slimscrolling", ~~delta);

                    // ensure bar is visible
                    showBar();

                    // trigger hide when scroll is stopped
                    hideBar();
                }

                function attachWheel(target) {
                    if (window.addEventListener) {
                        target.addEventListener("DOMMouseScroll", _onWheel, false);
                        target.addEventListener("mousewheel", _onWheel, false);
                    } else {
                        document.attachEvent("onmousewheel", _onWheel);
                    }
                }

                function getBarHeight() {
                    // calculate scrollbar height and make sure it is not too small
                    barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight);
                    bar.css({ height: barHeight + "px" });

                    // hide scrollbar if content is not long enough
                    var display = barHeight == me.outerHeight() ? "none" : "block";
                    bar.css({ display: display });
                }

                function showBar() {
                    // recalculate bar height
                    getBarHeight();
                    clearTimeout(queueHide);

                    // when bar reached top or bottom
                    if (percentScroll == ~~percentScroll) {
                        //release wheel
                        releaseScroll = o.allowPageScroll;

                        // publish approporiate event
                        if (lastScroll != percentScroll) {
                            var msg = (~~percentScroll == 0) ? "top" : "bottom";
                            me.trigger("slimscroll", msg);
                        }
                    } else {
                        releaseScroll = false;
                    }
                    lastScroll = percentScroll;

                    // show only when required
                    if (barHeight >= me.outerHeight()) {
                        //allow window scroll
                        releaseScroll = true;
                        return;
                    }
                    bar.stop(true, true).fadeIn("fast");
                    if (o.railVisible) {
                        rail.stop(true, true).fadeIn("fast");
                    }
                }

                function hideBar() {
                    // only hide when options allow it
                    if (!o.alwaysVisible) {
                        queueHide = setTimeout(function() {
                                if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) {
                                    bar.fadeOut("slow");
                                    rail.fadeOut("slow");
                                }
                            },
                            1000);
                    }
                }

            });

            // maintain chainability
            return this;
        }
    });

    $.fn.extend({
        slimscroll: $.fn.slimScroll
    });

})(jQuery);
